// Copyright (c) 2009 Mike Haight, Andy Kipp
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include "stdafx.h"

ResourceManager::ResourceManager(void)
{
}

void ResourceManager::Initialize(void)
{
    // Initialize Textures
    {
        InitializeTextures();
    }

    // Initialize Maps
    {
        InitializeMaps();
    }

    // Initialize Tiles
    {
        InitializeTiles();
    }

    // Initialize Quests
    {
        InitializeQuests();
    }
}


void ResourceManager::InitializeQuests(void)
{
        // Load all of the Maps
    smLogger->Info("Loading Quests.");

    XmlDocument doc;
    XmlSerializer deserializer(QuestDescription::typeid);

    try
    {
        doc.Load("data/quest/Quests.xml");

        for each (XmlNode^ child in doc.DocumentElement)
        {
            if (child->NodeType != XmlNodeType::Element)
                continue;

            XmlElement^ e = safe_cast<XmlElement^>(child);

            if (e->Name->Equals("Quest"))
            {
                smLogger->Info("\t{0} ({1})", e->GetAttribute("Name"), e->GetAttribute("Script"));
                QuestDescription questDesc = (QuestDescription)deserializer.Deserialize(gcnew XmlNodeReader(e));
                mQuests.Add(questDesc.Name, questDesc);
            }
            else
            {
                throw gcnew XmlException(String::Format("Invalid XmlElement name: {0}", e->Name));
            }
        }
    }
    catch (XmlException^ ex)
    {
        smLogger->ErrorException(L"Error reading Quest XML.", ex);
    }

    smLogger->Info("Loaded Quests.");
}


void ResourceManager::InitializeTextures(void)
{
    // Load all of the textures
    smLogger->Info("Loading Textures.");

    XmlDocument textureDoc;
    XmlSerializer textureDeserializer(Texture::typeid);

    try
    {
        textureDoc.Load("data/texture/Textures.xml");

        for each (XmlNode^ child in textureDoc.DocumentElement)
        {
            if (child->NodeType != XmlNodeType::Element)
                continue;

            XmlElement^ e = safe_cast<XmlElement^>(child);

            if (e->Name->Equals("Texture"))
            {
                smLogger->Info("\t{0} ({1}x{2})", e->GetAttribute("name"), e->GetAttribute("width"), e->GetAttribute("height"));
                Texture^ texture = (Texture^)textureDeserializer.Deserialize(gcnew XmlNodeReader(e));
                mTextures.Add(texture->Name, texture);
            }
            else
            {
                throw gcnew XmlException(String::Format("Invalid XmlElement name: {0}", e->Name));
            }
        }
    }
    catch (XmlException^ ex)
    {
        smLogger->ErrorException(L"Error reading Texture XML.", ex);
    }

    smLogger->Info("Loaded Textures.");
}


void ResourceManager::InitializeMaps(void)
{
    // Load all of the Maps
    smLogger->Info("Loading Maps.");

    XmlDocument doc;
    XmlSerializer deserializer(MapDescription::typeid);

    try
    {
        doc.Load("data/map/Maps.xml");

        for each (XmlNode^ child in doc.DocumentElement)
        {
            if (child->NodeType != XmlNodeType::Element)
                continue;

            XmlElement^ e = safe_cast<XmlElement^>(child);

            if (e->Name->Equals("Map"))
            {
                smLogger->Info("\t{0} ({1}x{2})", e->GetAttribute("Name"), e->GetAttribute("Rows"), e->GetAttribute("Columns"));
                MapDescription mapDesc = (MapDescription)deserializer.Deserialize(gcnew XmlNodeReader(e));
                mMaps.Add(mapDesc.Name, mapDesc);
            }
            else
            {
                throw gcnew XmlException(String::Format("Invalid XmlElement name: {0}", e->Name));
            }
        }
    }
    catch (XmlException^ ex)
    {
        smLogger->ErrorException(L"Error reading Map XML.", ex);
    }

    smLogger->Info("Loaded Maps.");
}


void ResourceManager::InitializeTiles(void)
{
    // Load all of the Tiles
    smLogger->Info("Loading Tiles.");

    XmlDocument doc;
    XmlSerializer deserializer(TileDescription::typeid);

    try
    {
        doc.Load("data/object/Tiles.xml");

        for each (XmlNode^ child in doc.DocumentElement)
        {
            if (child->NodeType != XmlNodeType::Element)
                continue;

            XmlElement^ mapElement = safe_cast<XmlElement^>(child);

            if (mapElement->Name->Equals("Map"))
            {
                mTiles.Add(mapElement->GetAttribute("Name"), gcnew Dictionary<int, TileDescription>());

                for each (XmlNode^ mapChild in mapElement)
                {
                    if (mapChild->NodeType != XmlNodeType::Element)
                        continue;

                    XmlElement^ tileElement = safe_cast<XmlElement^>(mapChild);

                    if (tileElement->Name->Equals("Tile"))
                    {
                        smLogger->Info("\t{0}:{1} ({2})", mapElement->GetAttribute("Name"), tileElement->GetAttribute("Name"), tileElement->GetAttribute("Type"));
                        TileDescription tileDesc = (TileDescription)deserializer.Deserialize(gcnew XmlNodeReader(tileElement));
                        mTiles[mapElement->GetAttribute("Name")]->Add(Int32::Parse(tileElement->GetAttribute("Type")), tileDesc);
                    }
                    else
                    {
                        throw gcnew XmlException(String::Format("Invalid XmlElement name: {0}", tileElement->Name));
                    }
                }
            }
            else
            {
                throw gcnew XmlException(String::Format("Invalid XmlElement name: {0}", mapElement->Name));
            }
        }
    }
    catch (XmlException^ ex)
    {
        smLogger->ErrorException(L"Error reading Tile XML.", ex);
    }

    smLogger->Info("Loaded Tiles.");
}

void ResourceManager::LoadObjects(String^ argFileName)
{
    // Load all of the Maps
    smLogger->Info("Loading Objects from {0}.", argFileName);

    XmlDocument doc;

    XmlAttributes^ attrs = gcnew XmlAttributes();

    XmlRootAttribute^ attr = gcnew XmlRootAttribute();
    attr->ElementName = "Monster";

    // Add the element to the collection of elements.
    attrs->XmlRoot = attr;

    // Create the XmlAttributeOverrides object.
    XmlAttributeOverrides^ attrOverrides = gcnew XmlAttributeOverrides();

    /* Add the type of the class that contains the overridden. */
    attrOverrides->Add(ObjectDescription::typeid, attrs);

    XmlSerializer deserializer(ObjectDescription::typeid, attrOverrides);

    try
    {
        doc.Load(argFileName);

        for each (XmlNode^ child in doc.DocumentElement)
        {
            if (child->NodeType != XmlNodeType::Element)
                continue;

            XmlElement^ e = safe_cast<XmlElement^>(child);
            smLogger->Info("\t{0} ({1})", e->GetAttribute("Name"), e->GetAttribute("Script"));
            ObjectDescription objDesc = (ObjectDescription)deserializer.Deserialize(gcnew XmlNodeReader(e));
            mObjects.Add(objDesc.Name, objDesc);
        }
    }
    catch (XmlException^ ex)
    {
        smLogger->ErrorException(L"Error reading Object XML.", ex);
    }

    smLogger->Info("Fnished loading Objects from {0}.", argFileName);
}

ObjectDescription^ ResourceManager::GetObject(String^ argObjectName)
{
    if (!mObjects.ContainsKey(argObjectName))
        throw gcnew Exception(String::Format("The ResourceManager does not contain an Object named '{0}'", argObjectName));

    ObjectDescription^ objDesc = %mObjects[argObjectName];

    if (!objDesc->Loaded)
    {
        smLogger->Info("Executing Object '{0}' Script '{1}'.", objDesc->Name, objDesc->Script);
        ScriptingEngine::Execute(objDesc->Script);
        objDesc->Loaded = true;
    }

    return objDesc;
}

QuestDescription^ ResourceManager::GetQuest(String^ argQuestName)
{
    if (!mQuests.ContainsKey(argQuestName))
        throw gcnew Exception(String::Format("The ResourceManager does not contain a Quest named '{0}'", argQuestName));

    QuestDescription^ questDesc = %mQuests[argQuestName];

    if (!questDesc->Loaded)
    {
        smLogger->Info("Executing Quest '{0}' Script '{1}'.", questDesc->Name, questDesc->Script);
        ScriptingEngine::Execute(questDesc->Script);
        questDesc->Loaded = true;
    }

    return questDesc;
}


TileDescription^ ResourceManager::GetTile(String^ argMapName, int argType)
{
    if (!mTiles.ContainsKey(argMapName))
        throw gcnew Exception(String::Format("The ResourceManager does not contain a Tile named '{0}:{1}'", argMapName, argType));

    if (!mTiles[argMapName]->ContainsKey(argType))
    {
        // TODO: This should throw an exception, but just log for now
        smLogger->Warn("The ResourceManager does not contain a Tile named '{0}:{1}'", argMapName, argType);
        
        // TODO: Just return a generic TileDescription for now
        TileDescription tileDesc;
        tileDesc.Name = "Unknown";
        tileDesc.Type = argType;
        return %tileDesc;
    }

    return %mTiles[argMapName][argType];
}


MapDescription^ ResourceManager::GetMap(String^ argMapName)
{
    if (!mMaps.ContainsKey(argMapName))
        throw gcnew Exception(String::Format("The ResourceManager does not contain a Map named '{0}'", argMapName));

    return %mMaps[argMapName];
}


Texture^ ResourceManager::GetTexture(String^ argTextureName)
{
    if (!mTextures.ContainsKey(argTextureName))
        throw gcnew Exception(String::Format("The ResourceManager does not contain a Texture named '{0}'", argTextureName));

    Texture^ texture = mTextures[argTextureName];
    texture->AddReference();
    return texture;
}

void ResourceManager::ReleaseTextures(void)
{
    for each (KeyValuePair<String^, Texture^> pair in mTextures)
    {
        delete pair.Value;
    }
}